try {
	// db.accounts.insertOne({ _id: "account1", name: "alice", balance: 100 })
	// db.accounts.insertOne({ name: "alice", balance: 100 })
	// db.accounts.find({})
	db.accounts.insertMany([{ name: "jake", balance: 100 }, { name: "david", balance: 80 }])
} catch (error) {
	print(error)
}

try {
	db.accounts.insertMany([{ name: "rose", balance: 808 }, { _id: "account1", name: "jake", balance: 100 }, { name: "david", balance: 80 }])
} catch (error) {
	print(error)
}

// 这是 ordered: false db自动决定写入顺序。id不重复的文档就会被写入。
// 按照顺序写入的时候，只要一遇到错误，就会退出写入操作。如果之前是正确的，就正确的文档仍然会被写入。
try {
	db.accounts.insertMany([{ _id: "account1", name: "jake", balance: 100 }, { name: "david", balance: 80 }], { ordered: false })
} catch (error) {
	print(error)
}

db.accounts.insert({ _id: "account1", name: "jake", balance: 100 }, { name: "david", balance: 80 })

// 获取时间 结果为： ISODate("2019-12-16T13:49:42Z")
ObjectId("5df78b763570d39979512a31").getTimestamp()

db.accounts.insert({ _id: { type: "savings", sex: "male" }, name: "jake", balance: 100 }, { name: "david", balance: 80 })
db.accounts.insert({ _id: { type: "savings", sex: "female" }, name: "haha", balance: 100 }, { name: "david", balance: 80 })

db.accounts.find({ name: "alice" }).pretty()

// 居然可以这样查询
db.accounts.find({ "_id.type": "savings" }).pretty()

/**
 * $eq 等于
 * $ne 不等于
 * $gt 大于
 * $gte 大于等于
 * $lt 小于
 * $lte 小于等于
 * $in 匹配任何与查询值相等的文档 （比较操作符）
 * $nin 匹配任何与查询值不相等的文档
 */
db.accounts.find({ name: { $eq: "alice" } }).pretty()
db.accounts.find({ name: { $in: ["david", "haha"] } }).pretty()
db.accounts.find({ name: { $nin: ["david", "haha"] } }).pretty()

// $ne 也会筛选出并不包含查询字段的文档
// { "_id" : ObjectId("5df78330c6a951381ee69aa8") }
// { "_id" : "account1", "name" : "alice", "balance" : 100 }
// {
//         "_id" : ObjectId("5df7863c3570d39979512a29"),
//         "name" : "alice",
//         "balance" : 100
// }
// {
//         "_id" : ObjectId("5df787443570d39979512a2a"),
//         "name" : "jake",
//         "balance" : 100
// }
// {
//         "_id" : ObjectId("5df787443570d39979512a2b"),
//         "name" : "david",
//         "balance" : 80
// }
// {
//         "_id" : ObjectId("5df788533570d39979512a2d"),
//         "name" : "david",
//         "balance" : 80
// }
// {
//         "_id" : ObjectId("5df7892b3570d39979512a2e"),
//         "name" : "rose",
//         "balance" : 808
// }
db.accounts.find({ "_id.type": { $ne: "savings" } }).pretty()

/**
 * $not 匹配不成立的条件
 * 读取不小于500的银行账户文档
 * 
 * 会筛选出不包含查询字段的文档
 */
db.accounts.find({ balance: { $not: { $lt: 500 } } })

/**
 * $and 需要符合所有希望成立的条件
 * 
 * 读取账户余额大于100
 * 用户姓名按照字母排序在fred之后
 */
// db.accounts.find({ balance: { $and: [{ $gt: 100 }, { $gt: "fred" }] } }) // 错误的写法
db.accounts.find({ $and: [{ balance: { $gt: 100 } }, { name: { $gt: "fred" } }] })

// 如果查询条件在不同字段上，那么简写为：
db.accounts.find(
	{
		balance: { $gt: 100 },
		name: { $gt: "fred" }
	}
)

// 当筛选条件应用在同一个字段上，也可以简化为 
db.accounts.find({ balance: { $gt: 0, $lt: 500 } })

/**
 * $or 相当于“或者”，最好使用 $in 命令替换
 * $nor 
 * 读取不属于alice和jake且余额不小于100的银行账户文档
 * 
 * 会筛选出不包含查询字段的文档
 */
db.accounts.find({
	$nor: [{ name: 'alice' }, { name: "jake" }, { balance: { $lt: 100 } }]
})

/**
 * $exists 匹配包含查询字段的文档
 * 读取包含账户类型字段的银行账户文档
 * 
 * 之前很多的操作符，会返回很多的不含查询字段的结果。添加 $exists 就可以免除这些多余信息了。
 */
db.accounts.find({
	"_id.sex": { $exists: true, $ne: "male" }
})

/**
 * $type 匹配字段类型符合查询值的文档
 * {filed:{$type:<BSON type>}}
 * {filed:{$type:[<BSON type1>, <BSON type2>, ...]}}
 * 
 * 读取文档主键是字符串的银行账户
 */
db.accounts.find({
	_id: { $type: "string" }
})

/**
 * objectId 一般的主键（例如：ObjectId("5df78330c6a951381ee69aa8")），注意这里必须小写
 * object 符合主键
 */
db.accounts.find({
	_id: { $type: ["objectId", "object"] }
})

/**
 * 这个找到 null 的文档
 * 这个也很实用
 */
db.accounts.find({
	name: { $type: "null" }
})

/**
 * 使用类型序号
 * 例如：
 * string 用 2 代替
 */
db.accounts.find({
	name: { $type: 2 }
})

/**
 * $all 筛选文档
 */
db.accounts.insert([
	{
		name: "jack",
		balance: 2000,
		contact: ['11111', "Alabama", "US"]
	},
	{
		name: "karen",
		balance: 2500,
		contact: [['22222', '3333333'], "Beijing", "China"]
	}
])
db.accounts.find({ contact: { $all: ['China', 'Beijing'] } })
db.accounts.find({ contact: { $all: [['22222', '3333333']] } })

/**
 * 读取联系电话范围在1000到200000之间的银行账户文档
 */
db.accounts.find({
	contact: {
		$elemMatch: { $gt: "10000000", $lt: "2000000" }
	}
})

/**
 * 将 $all $elemMatch 一起使用
 * 
 * all中的每一个条件都要满足
 * 
 * 读取包含一个在1000到20000之间，和一个在 1100 到 30000000 之间的联系电话的银行账户文档
 */
db.accounts.find(
	{
		contact: {
			$all: [
				{ $elemMatch: { $gt: "1000", $lt: "20000" } },
				{ $elemMatch: { $gt: "1100", $lt: "30000000" } }
			]
		}
	}
)

/**
 * $regex 正则表达式
 * 
 * 和 $in 一起使用，只能使用 /pattern/<options>
 * 例如：读取用户姓名 c或者j 开头的银行账户文档
 */
db.accounts.find({ name: { $in: [/^c/, /^j/] } })
/**
 * 读取用户姓名包含LIE（不区分大小写）的银行账户文档
 * i 表示 ignore 忽略大小写
 */
db.accounts.find({ name: { $regex: /AC/, $options: 'i' } })

// todo 正则表达式

// 游标
const c = db.accounts.find({});
// c 就是 游标

// 遍历游标文档
const myCursor = db.accounts.find({ name: "jack" })
while (myCursor.hasNext()) printjson(myCursor.next())
// forEach
const myCursor2 = db.accounts.find({ name: "jack" })
myCursor2.forEach((item) => { printjson(item) })

const myCursor3 = db.accounts.find({ name: "jack" })
myCursor3.forEach(printjson)

/**
 * limit(<number>)
 * skip(<offset>)
 * 
 * 返回一个文档，跳过第一个文档
 * 如果是limit(0)，表示不跳过，返回所有。
 */
db.accounts.find({ name: "jack" }).limit(1).skip(1)

/**
 * count(<applySkipLimit>)
 * 
 * applySkipLimit默认为false，不会有cursor.skip()和cursor.limit()的效果
 *  不提供筛选条件， cursor.count() 从元数据 Metadata 中得到结果。如果是多个数据库，元数据中的数量不一定准确，要尽量提供筛选条件。
 */
db.accounts.find({ name: "jack" }).limit(1).count() // 这里得到的是“2”
db.accounts.find({ name: "jack" }).limit(1).count(true) // 这里得到的是“1”

/**
 * sort(<document>)
 * <document> 定义了排序的要求
 * 
 * 1升序排列 -1降序排列
 * 
 * 按照余额降序排序，当余额相同，用户姓名按照字母排序升序排列银行账户文档
 */
db.accounts.find().sort({ balance: -1, name: 1 })

/**
 * 执行顺序
 * skip在limit之前执行
 * 
 * 先跳过前三个文档，从第四个开始得到五个文档
 */
db.accounts.find().limit(5).skip(3)

/**
 * 执行顺序
 * sort在skip和limit之前执行
 * 
 * 这里先排列账户余额，然后筛选。
 */
db.accounts.find().limit(5).skip(3).sort({ balance: -1 })

/**
 * 文档投影
 * find(<query>,<projection>)
 * 
 * projection取值 {filed:inclusion} 1表示返回字段，0表示不返回字段
 */
db.accounts.find({}, { name: 1 })
// 不返回文档主键
db.accounts.find({}, { name: 1, _id: 0 })
// 除了这两个，别的都返回。
db.accounts.find({}, { name: 0, _id: 0 })
// 除了文档主键，不可以在投影文档中混合使用包含和不包含两种投影
// 要么列出所有包含字段，要么列出所有不应该包含的字段。
// 不可以这样写：
db.accounts.find({}, { name: 0, balance: 1, _id: 0 })

/**
 * $slice 操作符，返回数组字段的部分元素
 */
db.accounts.find({}, { _id: 0, name: 1, contact: 1 })
// 返回操作数组中的第一个元素
db.accounts.find({}, { _id: 0, name: 1, contact: { $slice: 2 } })
// 倒数第一个元素
db.accounts.find({}, { _id: 0, name: 1, contact: { $slice: -1 } })

/**
 * $elemMatch和$操作符可以返回数组字段中满足筛选条件的第一个元素
 * 
 * 文档投影，返回name和“首个”联系方式中地名的字母顺序大于 Alabama 的文档
 * $elemMatch 在投影和别的不同
 * 
 * 这种方法会返回没有contact的文档投影
 */
db.accounts.find({}, {
	_id: 0,
	name: 1,
	contact: {
		$elemMatch: {
			$gt: "Alabama"
		}
	}
})
// 或者这种写法，只会返回有contact的文档投影。
db.accounts.find({ contact: { $gt: "Alabama" } }, { _id: 0, name: 1, "contact.$": 1 })
// 如果返回两个？ 要怎么做?

/**
 * 更新文档
 */

// 更新jack的银行账户余额和开户信息
db.accounts.update(
	{ name: "jack" },
	{
		$set: {
			balance: 3000,
			info: {
				dataOpened: new Date("2016-05-18T16:00:00z"),
				branch: "branch1"
			}
		}
	}
)

// 更新电话
db.accounts.update(
	{ name: "jack" },
	{
		$set: {
			"contact.0": "66666"
		}
	}
)

// 如果更新的元素下标大于数组长度，空的会被记录为null
db.accounts.update(
	{ name: "jack" },
	{
		$set: {
			"contact.5": "更新第六个元素，空的记录是null"
		}
	}
)

/**
 * 删除字段
 * $unset 的赋值 "" 对结果没有任何影响。
 */
db.accounts.update(
	{ name: "jack" },
	{
		$unset: {
			balance: "adfadfa", // 还是会删掉 balance
			"info.branch": ""
		}
	}
)

db.accounts.update(
	{ name: "jack" },
	{
		$unset: {
			"contact.0": "" // 将当前值设定为 null，不会让数组长度减1
		}
	}
)

/**
 * 将那么属性重命名为“contact”，那么原来的contact将会被删除。
 */
db.accounts.update({ name: "karen" }, { $rename: { "name": "contact" } })

/**
 * 修改属性所在的位置
 */
// db.accounts.update({ _id: ObjectId("5df821fc58d2d1f4f8506ac8") }, { $set: { "info.balance": 10086 } })
db.accounts.update({ name: "jack" }, { $rename: { "info.balance": "balance" } })
/**
 * 如果是下面这样的写法，会报错的。
 * 
 * $rename 的新旧字段都不能指向数组元素。注意和 $set/$unset 对比。 
 */
db.accounts.update({ name: "jack" }, { $rename: { "info.3.balance": "balance" } })
db.accounts.update({ name: "jack" }, { $rename: { "balance": "info.3.balance" } })

/**
 * 更新操作符
 * 
 * $inc 增加数据
 * $mul 乘以数据
 * 
 * 如果不是数据会报错
 * 
 * $inc 如果没有这个字段，将会直接写入这个数据
 * $mul 如果没有这个字段，将会写入0
 */
db.accounts.update({ name: "jack" }, { $inc: { "balance": -1.5 } })
db.accounts.update({ name: "jack" }, { $mul: { "balance": -10 } })

/**
 * $min 比较两个值，最小的那个写入
 * $max 相反
 * 
 * 还可以比较“时间”
 */
db.accounts.update({ name: "jack" }, { $min: { "balance": -10 } })
db.accounts.update({ name: "jack" }, { $max: { "balance": -10 } })

/**
 * $addToSet 如果是重复的值，不会添加
 * 如果插入的是数组和文档，插入值的字段顺序和内容完全重复，才会被忽略。
 */
db.accounts.update({ name: "jack" }, { $addToSet: { contact: "China" } })
/**
 * 插入的数组将会被当做内嵌数组
 * $each 就不会被当做内嵌数组
 */
db.accounts.update({ name: "jack" }, { $addToSet: { contact: ['美国', '圣地亚哥'] } })
db.accounts.update({ name: "jack" }, { $addToSet: { contact: { $each: ['美国', '圣地亚哥'] } } })

/**
 * $pop 从数组中删除元素，删除所有数据后，会留下空数组。
 * $pop 1 从数组中删除最后一个元素
 */
db.accounts.update({ name: "jack" }, { $pop: { contact: 1 } })
db.accounts.update({ name: "jack" }, { $pop: { "contact.8": -1 } }) // 内嵌数组中删除

/**
 * $pull 从数组字段中删除特定元素
 * 注意和 find insert 对比
 * todo 为什么这个代码得不到教程中的结果 ?
 */
try {
	db.accounts.find({ name: "karen" }).forEach(function (doc) {
		var temp = { ...doc }
		temp.name = "lawrence"
		// temp._id = null
		// temp.forEach(function (item) {
		// 	printjson(item)
		// })
		let newTemp = {}

		for (const key in temp) {
			if (temp.hasOwnProperty(key)) {
				const element = temp[key];
				if (key !== '_id')
					newTemp[key] = element
			}
		}

		printjson(newTemp)

		var result = db.accounts.insert(newTemp)
		printjson(result)
	})
} catch (error) {
	print(error)
}

/**
 * update 和 $pull
 * 组合删除数组元素
 * 这里别用 $elemMatch ，因为 $pull 只能作用在数组中
 */
db.accounts.update({ name: "lawrence" }, { $pull: { contact: { $regex: /hi/ } } })
/**
 * 删除符合条件的整个内嵌数组
 * 这个会将含有 "22222" 元素的整个内嵌数组删除
 * 
 */
db.accounts.update({ name: "lawrence" }, { $pull: { contact: { $elemMatch: { $eq: "22222" } } } })

/**
 * $pullAll 命令相当于
 * { $pull: { contact: { $in: ['22222','333333'] } } }
 * 要删除的内嵌数组的元素内容和排列顺序要完全相同。
 */
db.accounts.update({ name: "lawrence" }, { $pull: { contact: { $in: ['22222', '333333'] } } })

/**
 * $push 向数组字段中添加元素
 * 和 $each 搭配使用，会达到什么效果 ???
 * 和 $position 搭配使用，插入到数组指定位置。可以是整数或者负数。
 * 和 $sort 搭配使用，排序操作。注意：必须同时使用 $push 和 $each 两个操作符。
 */
db.accounts.update({ name: "lawrence" }, { $push: { newArray: "new element" } })

/**
 * 内嵌文档中value字段，倒序排序。
 */
db.accounts.update({ name: "lawrence" }, {
	$push: {
		newArray2: {
			$each: [
				{ key: "sort", value: 100 },
				{ key: "sort", value: 200 }
			],
			$sort: {
				value: -1
			}
		}
	}
})

/**
 * 不插入元素，仅仅排序
 */
db.accounts.update({ name: "lawrence" }, {
	$push: {
		newArray2: {
			$each: [],
			$sort: {
				value: 1
			}
		}
	}
})

/**
 * $slice 截取部分数组
 * 
 * 下面达到的效果是：
 * 在lawrence中的newArray2数组中添加元素slice1，然后截取倒数两个元素保留在数组newArray2中。
 * 
 * 如果不想插入元素，仅仅截取文档中的数组字段，$each[] 这样就可以了。
 */
db.accounts.update({ name: "lawrence" }, {
	$push: {
		newArray2: {
			$each: ["slice1"],
			$slice: -2
		}
	}
})

/**
 * $position,$sort,$slice搭配使用
 * 执行顺序是
 * $position
 * $sort
 * $slice
 */
db.accounts.update({ name: "lawrence" }, {
	$push: {
		newArray2: {
			$each: ["slice1"],
			$slice: -2
		}
	}
})

/**
 * $ 占位符
 * 
 * 更新lawrence的newArray数组中的push2为updated
 * 只会更新第一个符合匹配的元素
 * 
 */
db.accounts.update(
	{
		name: "lawrence",
		newArray: "push2"
	},
	{
		$set: {
			"newArray.$": "updated"
		}
	}
)

/**
 * $[] 占位符
 * 更新匹配的所有元素
 * 
 * 更新contact数组中的所有元素为  "updated $[]"
 */
db.accounts.update(
	{
		name: "lawrence"
	},
	{
		$set: {
			"contact.$[]": "updated $[]"
		}
	}
)

/**
 * update的options选项
 * 
 * {multi:<boolean>} 更新多个文档，目前为止只会更新首个匹配的文档。
 * 注意multi的原子性功能限制，需要使用事务功能。
 */

/**
 * upsert 这是 update + insert 的意思
 * 如果update命令中的筛选条件无法匹配现有文档，那么就创建这个文档。
 */
db.accounts.update(
	{ name: "maggie" },
	{ $set: { balance: 300 } },
	{ upsert: true }
)

/**
 * 如果筛选条件不确定，创建的新文档不会包含筛选条件
 */
db.accounts.update(
	{ balance: { $gt: 200000 } },
	{ $set: { name: 'nick' } },
	{ upsert: true }
)

/**
 * save 保存文档
 * 
 * 如果_id相同，相当于运行 update 命令
 */
db.accounts.save({
	_id: "account1",
	name: "alice2",
	balance: 10089
})

/**
 * remove 命令会删除所有符合条件的文档
 * justOne 删除第一篇符合条件的文档
 */
db.accounts.remove({
	balance: { $lt: 1000 },
}, {
	justOne: true
})

// 删除所有文档
db.accounts.remove({})

// 删除集合
db.accounts.drop()

/**
 * 文档太多，remove效率不高，使用drop再创建空的集合和索引。
 */

/**
 * 聚合操作
 * aggregate 返回的是 “文档游标” ，不影响原本数据库中的操作。
 */
db.accounts.insertMany(
	[
		{
			name: { firstName: "alice", lastName: "wong" },
			balance: 50
		},
		{
			name: { firstName: "bob", lastName: "yang" },
			balance: 20
		}
	]
)
// 对银行账户文档进行重新投影
db.accounts.aggregate([
	{
		$project: {
			_id: 0,
			balance: 1,
			clientName: "$name.firstName"
		}
	}
])

// 没有的数组元素会被 null 代替
// $project 是一个常用的聚合阶段
db.accounts.aggregate([
	{
		$project: {
			_id: 0,
			balance: 1,
			nameArray: ["$name.firstName", "$name.middleName", "$name.lastName"]
		}
	}
])

/**
 * 聚合管道阶段
 * 
 * $match
 * 筛选账户文档
 */
db.accounts.aggregate([
	{
		$match: {
			"name.firstName": "alice"
		}
	}
])
// 筛选出银行账户在40-80元，或者lastName是yang的文档
db.accounts.aggregate([
	{
		$match: {
			$or: [
				{ balance: { $gt: 40, $lt: 80 } },
				{ "name.lastName": "yang" }
			]
		}
	}
])
/**
 * 将筛选和投影结合
 */
db.accounts.aggregate([
	{
		$match: {
			$or: [
				{ balance: { $gt: 40, $lt: 80 } },
				{ "name.lastName": "yang" }
			]
		}
	},
	{
		$project: {
			_id: 0
		}
	}
])

/**
 * 筛选/跳过第一篇
 * 不知道为什么这两个不能混用。
 */
db.accounts.aggregate([
	{ $limit: 1 }
])
db.accounts.aggregate([
	{ $skip: 1 }
])

/**
 * 将文档中的货币种类数组展开
 * 这里会得到三个文档。新生成的文档别的都一样，currency是一个原来的元素。
 */
db.accounts.aggregate([
	{
		$unwind: {
			path: "$currency"
		}
	}
])

// 展开数组添加元素位置
db.accounts.aggregate([
	{
		$unwind: {
			path: "$currency",
			includeArrayIndex: "ccyIndex"
		}
	}
])

/**
 * 下面这三种情况，
 * unwind不会理会。
 * 会被unwind操作阶段剔除掉。
 * 
 * 如果使用
 * preserveNullAndEmptyArrays: true
 * 就不会被剔除
 */
db.accounts.insertMany([
	{
		name: { first: 'aaa', lastName: 'aaaLastName' },
		balance: 100
	},
	{
		name: { first: 'bbb', lastName: 'bbbLastName' },
		balance: 200,
		currency: []
	},
	{
		name: { first: 'ccc', lastName: 'cccLastName' },
		balance: 20,
		currency: null
	}
])

db.accounts.aggregate([
	{
		$unwind: {
			path: "$currency",
			preserveNullAndEmptyArrays: true
		}
	}
])

/**
 * $sort 管道排序
 */
db.accounts.aggregate([
	{
		$sort: {
			balance: 1,
			"name.lastName": -1
		}
	}
])

/**
 * $lookup 
 * 使用单一字段值查询
 * 
 * 同一个数据库中的另一个集合
 * 管道文档中用来进行查询的字段
 * 另一个集合的查询字段
 * 写入管道文档中的查询结果数组字段
 */

// 增加一个集合存储外汇数据
// ccy currency 货币
db.forex.insertMany([
	{
		ccy: 'USD',
		rate: 6.91,
		date: new Date('2018-12-21')
	},
	{
		ccy: 'GBP',
		rate: 8.72,
		date: new Date('2018-08-21')
	},
	{
		ccy: 'CNY',
		rate: 1.0,
		date: new Date('2018-12-21')
	}
])

/**
 * 将查询到的外汇汇率写入银行账户文档
 * 
 * 这一句得会将forex中含有ccy的所有文档数据和accounts中的currency对比，如果相同则写入每一项含有currency的accounts文档中。
 */
db.accounts.aggregate([
	{
		$lookup: {
			from: 'forex',
			localField: 'currency',
			foreignField: 'ccy',
			as: 'forexData'
		}
	}
]) // 这里得到的 forexData 是一个数组

db.accounts.aggregate([
	{
		$lookup: {
			from: 'forex',
			localField: 'currency',
			foreignField: 'ccy',
			as: 'forexData'
		}
	},
	{
		$unwind: {
			path: '$forexData'
		}
	}
]) // 这里得到的 forexData 是一个对象

/**
 * 和 unwind 配合使用
 * 会得到三篇文档
 */
db.accounts.aggregate([
	{
		$unwind: {
			path: '$currency'
		}
	},
	{
		$lookup: {
			from: 'forex',
			localField: 'currency',
			foreignField: 'ccy',
			as: 'forexData'
		}
	}
])

/**
 * 复杂条件
 * 
 * 将特定日期外汇汇率写入银行账户文档
 * 
 * 这是“不相关查询”，从forex中得到的数据放到管道中匹配出对应的日期，然后和accounts中的数据匹配成为新的数据。
 * 也就是塞到accounts中的每一个数据中。
 */
db.accounts.aggregate(
	[
		{
			$lookup: {
				from: "forex",
				pipeline: [
					{
						$match: { date: new Date("2018-12-21") }
					}
				],
				as: 'forexData'
			}
		},
		{
			$project: {
				_id: 0,
				name: 1,
				forexData: 1
			}
		}
	]
)

/**
 * 如果银行账户大于100，就显示2018-12-21的外汇数据
 * 
 * $expr expression 只有在这里面才能操作let声明的变量
 * 
 * 不显示forexData === [] 的文档
 */
db.accounts.aggregate([
	{
		$lookup: {
			from: 'forex',
			let: { bal: '$balance' },
			pipeline: [{
				$match: { $expr: { $and: [{ $eq: ['$date', new Date('2018-12-21')] }, { $gt: ['$$bal', 100] }] } }
			}
			],
			as: 'forexData'
		}
	},
	{
		$project: {
			_id: 0,
			name: 1,
			"forexData": {
				$cond: {
					if: { $eq: [[], '$forexData'] },
					then: '$$REMOVE',
					else: '$forexData'
				}
			}
		}
	}
])


/**
 * $group
 */
db.transactions.insertMany([
	{ "symbol": '600519', "qty": 100, "price": 567.4, currency: 'CNY' },
	{ "symbol": 'AMZN', "qty": 1, "price": 1377.5, currency: 'USD' },
	{ "symbol": 'AAPL', "qty": 2, "price": 150.7, currency: 'USD' }

])

/**
 * 聚合操作符计算分组聚合值
 * 
 * 根据外汇货币分组。并且分别计算出他们的：
 * 总交易次数 totalQty
 * 总的交易价格 totalNotional
 * 等等
 */
db.transactions.aggregate([
	{
		$group: {
			_id: "$currency",
			totalQty: { $sum: "$qty" },
			totalNotional: { $sum: { $multiply: ['$price', '$qty'] } },
			avgPrice: { $avg: "$price" },
			count: { $sum: 1 },
			maxNotional: { $max: { $multiply: ['$price', '$qty'] } },
			minNotional: { $min: { $multiply: ['$price', '$qty'] } }
		}
	}
])

// 用 null 来分组，就是不分组。
db.transactions.aggregate([
	{
		$group: {
			_id: null,
			totalQty: { $sum: "$qty" },
			totalNotional: { $sum: { $multiply: ['$price', '$qty'] } },
			avgPrice: { $avg: "$price" },
			count: { $sum: 1 },
			maxNotional: { $max: { $multiply: ['$price', '$qty'] } },
			minNotional: { $min: { $multiply: ['$price', '$qty'] } }
		}
	}
])

/**
 * symbol 股票编号
 * 将所有股票按照币种分组，然后将symbol写入数组中。
 * 
 * B basic 基本
 */
db.transactions.aggregate([
	{
		$group: {
			_id: '$currency',
			symbols: { $push: "$symbol" }
		}
	}
])

/**
 * 按照交易货币来分组交易记录
 * 必须提供 _id。按照 currency 来分组
 */
db.transactions.aggregate([
	{
		$group: {
			_id: "$currency"
		}
	}
])

/**
 * $out 聚合管道中的文档写入新的集合 output 中
 * 
 * 如果出错，output集合不会创建或者更新
 */
db.transactions.aggregate([
	{
		$group: {
			_id: "$symbol",
			totalNotional: {
				$sum: {
					$multiply: [
						"$price",
						"$qty"
					]
				}
			}
		}
	},
	{
		$out: "output"
	}
])

/**
 * allowDiskUse:<boolean>
 * 如果聚合管道内存上限超出了100MB，这个就很有用。
 */

/**
 * 优化
 * $match 阶段会尽量在  $project 阶段之前运行。这样会操作更少的文档。
 */

/**
 * mongodb 对指定字段进行排序的数据结构 B-treeb 
 */

/**
 * 创建索引
 */
db.accountsWithIndex.insertMany([
	{
		name: 'alice', balance: 50, currency: ['GBP', 'USD']
	},
	{
		name: 'bob', balance: 20, currency: ['AUD', 'USD']
	},
	{
		name: 'bob', balance: 300, currency: ['CNY']
	},
])

/**
 * 单建索引
 */
db.accountsWithIndex.createIndex({ name: 1 })
/**
 * 复合建索引
 */
db.accountsWithIndex.createIndex({ name: 1, balance: -1 })
/**
 * 多键索引
 * 在数组字段中创建
 */
db.accountsWithIndex.createIndex({ currency: 1 })

/**
 * 索引的效果
 */
db.accountsWithIndex.explain().find({ balance: 100 })
// 这个没有fetch阶段。
db.accountsWithIndex.explain().find({ name: 'alice' }, { _id: 0, name: 1 })
// 这个是排序，注意这里的 inputStage 是 IXSCAN
db.accountsWithIndex.explain().find().sort({ name: 1, balance: -1 })

/**
 * 删除索引
 */
db.accountsWithIndex.dropIndex({ name: 1, balance: -1 })

/**
 * 创建索引的options
 * 索引的三种特性
 * 唯一性，文档主键_id上有一个默认索引。创建一个具有唯一性的索引
 */
// 强制性要求存入的balance的值是不同的。
db.accountsWithIndex.createIndex({ balance: 1 }, { unique: true })
/**
 * 如果已有文档中的某个字段出现了重复值，就不可以在这个字段上创建唯一性索引。
 * 下面这个，第一次创建能够成功，这里的balance是null。
 * 如果第二次插入的文档也没有balance，那么就会重复null值，破坏了唯一性。不允许插入。
 */
db.accountsWithIndex.insert({ name: 'hahha', lastAccess: new Date() })
/**
 * 删除索引
 */
db.accountsWithIndex.dropIndex('balance_1')
/**
 * 索引的稀疏性
 * 只要文档中包含索引键，即使是null，也会被加入索引的数据结构上。
 * 
 * 如果同一个索引具有唯一性和稀疏性，就可以保存多篇缺失索引键值的文档。
 * 
 * 说白了就是如果不加 spare:true 只保留 unique:true 那么重复添加这一句
 * db.accountsWithIndex.insert({ name: 'david', lastAccess: new Date() })
 * 就会破坏balance的唯一性，加了就不会。
 */
db.accountsWithIndex.createIndex({ balance: 1 }, { sparse: true, unique: true })
db.accountsWithIndex.insert({ name: 'david', lastAccess: new Date() })
/**
 * 在lastAccess字段上创建一个生存时间是20秒的索引
 */
db.accountsWithIndex.createIndex({ lastAccess: 1 }, { expireAfterSeconds: 20 })
/**
 * 数组的复合建索引不具备生存时间特性
 * 当索引是包含日期元素的数组字段，数组中最小的日期将被用来计算文档是否已经过期
 * 数据库使用一个后台线程监测和删除过期文档，删除操作可能有一定的延迟。
 */


/**
 * 获取所有的索引
 */
db.accountsWithIndex.getIndexes()
db.accountsWithIndex.find()
db.forex.find({})
db.accounts.find({})
db.accounts.find({}).pretty()
db.accounts.find({ name: 'lawrence' }).pretty()
db.accounts.remove({ name: 'lawrence' })